from Box2D import b2World, b2BodyDef, b2_staticBody, b2PolygonShape, \
    b2FixtureDef, b2CircleShape, b2ContactListener
from Actor import Actor, Ball
from Specials import Brick
from Cannon import Cannon
from libavg import avg, ui
from random import randint
import Constants
import math
from components.Specials import Event, Gravity, Bumper
from Box2D.Box2D import b2Vec2

class Level(avg.DivNode):
    
    __ITEM_SWITCHED = False  
    
    def __init__(self, parent, xml,  **kwargs):
        super(Level, self).__init__(**kwargs)

        if parent:
            parent.appendChild(self)   
            self.__board = parent  
        
        self.__xml = xml    
        self.__players = self.__board.getPlayers()
        self.__finished = False
        self.__itemProbability = 0
        self.__background = None
        self.__walls = []

        self.size = parent.size
        self.pos = (0, 0)

        self.world = World(parent=self, gravity=(0,0))
        self.bricks = []
        self.balls = []
        self.actors = []
        self.cannons = []
        self.bubbles = []
        self.items = [] 
        self.events = []
        
        # create the world boundary^
        if (self.__players > 1):
            self.__walls.append(Wall(parent=self, size=(15, self.size.y), actorId=0, position=(-15, 0)))
        self.__walls.append(Wall(parent=self, size=(self.size.x, 15), position=(0, self.size.y)))
        self.__walls.append(Wall(parent=self, size=(15, self.size.y), actorId=1, position=(self.size.x, 0)))
        self.__walls.append(Wall(parent=self, size=(self.size.x, 15), position=(0, -15)))
        
        # create remove lists
        self.__removeBricks = []
        self.__removeBalls = []
        self.__removeActors = []
        self.__removeCannons = []
        self.__removeBubbles = []
        self.__removeItems = []
        self.__removeEvents = []
        
        self.__parse()
        
    def getXml(self):
        return self.__xml    
        
    def enter(self):
        self.world.start()
        self.__onFrameHandler = avg.Player.get().setOnFrameHandler(self.__update)
        
    def leave(self):
        avg.Player.get().clearInterval(self.__onFrameHandler)
        self.world.stop()
        
    def __update(self):
        levelAbort = False
        if self.__players == 2:
            levelAbort = self.actors[1].shuttles.__len__() == 0
        levelAbort = levelAbort or (self.actors[0].shuttles.__len__() == 0)
        
        if levelAbort:
            self.__board.endLevel(False)
            return
        elif self.bricks.__len__() == 0:
            self.__board.endLevel(True)
            return

        self.__clear()
        self.__handleBonus()    
            
    def __handleBonus(self):
        if self.__ITEM_SWITCHED == True:
            self.__ITEM_SWITCHED = False
            self.__switchBalls()
            
    def __clear(self):
        # remove bricks
        for brick in self.__removeBricks:
            if brick is not None:
                brick.destroy()
                self.bricks.remove(brick)
        del self.__removeBricks[:]
        self.__removeBricks = []
        # remove balls
        for ball in self.__removeBalls:
            if ball is not None:
                ball.destroy()
                self.balls.remove(ball)
        del self.__removeBalls[:]
        self.__removeBalls = []
        # remove actors
        for actor in self.__removeActors:
            if actor is not None:
                actor.destroy()
                self.actors.remove(actor)
        del self.__removeActors[:]
        self.__removeActors = []
        # remove cannons
        for cannon in self.__removeCannons:
            if cannon is not None:
                cannon.destroy()
                self.cannons.remove(cannon)  
        del self.__removeCannons[:]
        self.__removeCannons = []      
        # remove bubbles
        for bubble in self.__removeBubbles:
            if bubble is not None:
                bubble.destroy()
                self.bubbles.remove(bubble) 
        del self.__removeBubbles[:] 
        self.__removeBubbles = [] 
        # remove items
        for item in self.__removeItems:
            if item is not None:
                item.destroy()
                self.items.remove(item) 
        del self.__removeItems[:] 
        self.__removeItems = []
        # remove gravities
        for event in self.__removeEvents:
            if event is not None:
                event.destroy()
                self.events.remove(event)  
        del self.__removeEvents[:]
        self.__removeEvents = []
            
    def DestroyBall(self, ball):
        self.__removeBalls.append(ball)
        
    def DestroyBrick(self, brick):
        self.__removeBricks.append(brick)
        
    def DestroyActor(self, actor):
        self.__removeActors.append(actor)
        
    def DestroyCannon(self, cannon):
        self.__removeCannons.append(cannon)
        
    def DestroyBubble(self, bubble):
        self.__removeBubbles.append(bubble)

    def DestroyItem(self, item):
        self.__removeItems.append(item)

    def DestroyEvent(self, event):
        self.__removeEvents.append(event)

    def destroy(self):
        if self.__onFrameHandler:
            avg.Player.get().clearInterval(self.__onFrameHandler)
        self.__onFrameHandler = None
        for brick in self.bricks:
            self.DestroyBrick(brick)
        for actor in self.actors:
            self.DestroyActor(actor)
        for ball in self.balls:
            self.DestroyBall(ball)
        for cannon in self.cannons:
            self.DestroyCannon(cannon)  
        for bubble in self.bubbles:
            self.DestroyBubble(bubble)
        for item in self.items:
            self.DestroyItem(item)
        for event in self.events:
            self.DestroyEvent(event)
            
        self.__clear()

        for wall in self.__walls:
            wall.destroy()
        self.__walls = []
        
        self.world.destroy()
        self.world = None
        
        self.__btnExit1.unlink(True)
        self.__btnExit1 = None
        self.__lTapRecognizerExit1.enable(False)
        self.__lTapRecognizerExit1 = None
        
        if self.__board.getPlayers() == 2:
            self.__btnExit2.unlink(True)
            self.__btnExit2 = None
            self.__lTapRecognizerExit2.enable(False)
            self.__lTapRecognizerExit2 = None
        
        self.__background.unlink(True)
        self.__background = None
        self.__actorSvg = None
        self.__actorLine1.unlink(True)
        self.__actorLine1 = None
        if self.__actorLine2 is not None:
            self.__actorLine2.unlink(True)
            self.__actorLine2 = None
        self.__blackHole.unlink(True)
        self.__blackHole = None
        self.__bumper.unlink(True)
        self.__bumper = None
        
        self.unlink(True)

    def __parse(self):
        bgr = eval("int("+self.__xml.getAttribute("bgr")+")")
        if not bgr:
            bgr = 1
        self.__background = avg.ImageNode(parent=self, href="resources/textures/level/bgr_" + bgr.__str__() + ".png", size=self.size, pos=(0, 0))
        
        self.__itemProbability = eval("int("+self.__xml.getAttribute("itemProbability")+")")
        self.__parseItems()

        if(eval("int("+self.__xml.getAttribute("cannons")+")") == 1):
            self.cannons.append(Cannon(parent=self, cannonId=0))
            self.cannons.append(Cannon(parent=self, cannonId=1))

        actors = self.__xml.getElementsByTagName("actor")
        self.__actorSvg = avg.SVG("resources/textures/level/actorLine.svg", False)
        self.__actorLine1 = self.__actorSvg.createImageNode('Layer1', dict(parent=self, pos=(Constants.RESOLUTION.x/4, 0)), (1, Constants.RESOLUTION.y))
        if actors.__len__() == 2:        
            self.__actorLine2 = self.__actorSvg.createImageNode('Layer1', dict(parent=self, pos=(Constants.RESOLUTION.x/4*3, 0)), (1, Constants.RESOLUTION.y))
        else:
            self.__actorLine2 = None
            
        '''        
            gravities = self.__xml.getElementsByTagName("gravity")
            for gravity in gravities:
            self.__parseGravity(gravity)
        '''
        self.__blackHole = Event(parent=self, node=Gravity(parent=self), eventHandler=self.__gravityEvent, intervalHandler=self.__gravityInterval, maxDuration=7000, maxInterval=10000)
        self.__bumper = Event(parent=self, node=Bumper(parent=self), eventHandler=self.__bumpEvent, intervalHandler=self.__bumpInterval, maxDuration=5000, maxInterval=40000)
        self.events.append(self.__blackHole)
        self.events.append(self.__bumper)

        i = 0
        for actor in actors:
            self.__parseActor(actor, i)
            i += 1

        self.__btnExit1 = avg.ImageNode(parent=self, href="resources/textures/level/btn_exit.png", size=(50,50), pos=(5, Constants.RESOLUTION.y - 55))
        self.__lTapRecognizerExit1 = ui.HoldRecognizer(
                                                        node=self.__btnExit1, 
                                                        delay=1500,
                                                        detectedHandler=self.__onExitBtn
                                                      )

        if self.__board.getPlayers() == 2:
            self.__btnExit2 = avg.ImageNode(parent=self, angle= 180 * Constants.DEGREE2RAD ,href="resources/textures/level/btn_exit.png", size=(50,50), pos=(Constants.RESOLUTION.x - 55, 5))
            self.__lTapRecognizerExit2 = ui.HoldRecognizer(
                                                            node=self.__btnExit2, 
                                                            delay=1500,
                                                            detectedHandler=self.__onExitBtn
                                                          )
            
        bricks = self.__xml.getElementsByTagName("brick")

        for brick in bricks:
            self.__parseBrick(brick)
    
    def getRandomItemID(self):
        probabs = self.getItemProbabilities()
         
        myId = 0
        randValue = randint(1, 100)
        upVal = probabs[myId]
    
        while randValue > upVal:
            myId += 1
            upVal += probabs[myId]
            
        return myId
    
    def getItemProbabilityForLevel(self):
        return self.__itemProbability
    
    def getItemProbabilities(self):
        return self.__itemProbabilities       
            
    def __gravityEvent(self, event):
        node = event.getNode()
        node.pos = (randint(0, self.size.x - node.size.x / 2), randint(0, self.size.y - node.size.y / 2))
        
    def __gravityInterval(self, event):
        pass
    
    def __bumpEvent(self, event):
        node = event.getNode()
        node.pos = (randint(0, self.size.x - node.getRadius()), randint(0, self.size.y - node.getRadius()))        
        node.setBodyPosition(b2Vec2((node.pos.x + node.getRadius()) * Constants.PIXELS2METERS, (node.pos.y + node.getRadius()) * Constants.PIXELS2METERS))
    
    def __bumpInterval(self, event):
        node = event.getNode()
        node.setBodyPosition(b2Vec2(-400, -400))
        
    ######################################       
            
    def __onExitBtn(self, event):
        self.__board.abortLevel()
    
    def __parseItems(self):
        items = self.__xml.getElementsByTagName("item")
        self.__itemProbabilities = []
        
        for i in items:
            self.__itemProbabilities.append(eval("int("+i.getAttribute("probability")+")")) 
    
    def __parseActor(self, actor, actorId):
        a = Actor(parent=self, actorId=actorId)
        self.actors.append(a)
        
        shuttles = actor.getElementsByTagName("shuttle")
        for shuttle in shuttles:
            a.CreateShuttle(shuttle)
            
        balls = actor.getElementsByTagName("ball")
        for ball in balls:
            self.__parseBall(ball, actorId)
       
    
    def __parseBall(self, ball, actorId):
        x = eval("float("+ball.getAttribute("x")+")")
        y = eval("float("+ball.getAttribute("y")+")")
        radius = eval("float("+ball.getAttribute("radius")+")")
        b = Ball(parent=self, actorId=actorId, radius=radius, position=(x, y), speed=8)
        self.balls.append(b)
        
    def __parseEvent(self, event):
        x = eval("float("+event.getAttribute("x")+")")
        y = eval("float("+event.getAttribute("y")+")")
        radius = eval("float("+event.getAttribute("radius")+")")
        g = Gravity(parent=self, position=(x, y), radius=radius)
        e = Event(parent=self, node=g)
        self.events.append(e)        
    
    def __parseBrick(self, brick):
        x = eval("float("+brick.getAttribute("x")+")")
        y = eval("float("+brick.getAttribute("y")+")")
        width = eval("float("+brick.getAttribute("width")+")")
        height = eval("float("+brick.getAttribute("height")+")")
        life = eval("int("+brick.getAttribute("life")+")")
        angle = eval("int("+brick.getAttribute("angle")+")") * (math.pi / 4)
        b = Brick(parent=self, size=(width, height), life=life, position=(x, y), angle=angle)
        self.bricks.append(b)
    
    def __switchBalls(self):
        if self.__board.getPlayers() == 2:
            for ball in self.balls:
                ball.setActorID(1 - ball.getActor())

    def bonus(self, itemId):
        if itemId == Constants.ITEM_SWITCH: 
            self.__ITEM_SWITCHED = True
  
class Wall(avg.RectNode):
    def __init__(self, parent, position, size, actorId=None, **kwargs):
        super(Wall, self).__init__(**kwargs)

        if parent:
            parent.appendChild(self)
            self.__level = parent
   

        self.__actorId = actorId
        self.__body = self.__level.world.Rectangle(position=position, size=size)
        self.__body.userData = self
        self.__body.fixtures[0].filterData.maskBits = 176
        self.__body.fixtures[0].filterData.categoryBits = 1

        self.size = size
        self.pos = position
        self.angle = self.__body.angle
        
    def getActor(self):
        return self.__actorId

    def onBeginContact(self, o):
        pass
          
    def onEndContact(self, o):
        pass
    
    def destroy(self):
        self.__level.world.DestroyBody(self.__body)
        self.__body = None
        self.unlink(True)


class World(avg.DivNode):
                
    def __init__(self, parent, gravity, **kwargs):
        super(World, self).__init__(**kwargs)
        
        if parent:
            parent.appendChild(self)   
            self.size = parent.size
        
        self.__gravity = gravity
        self.__create()
        
    def __create(self):
        self.__world = b2World(gravity = self.__gravity, doSleep = True)
        self.__world.contactListener = CollisionListener()
        self.__world.warmStarting = False
        
    def start(self):
        self.__onFrameHandler = avg.Player.get().setOnFrameHandler(self.__update)
    
    def stop(self):
        avg.Player.get().clearInterval(self.__onFrameHandler)
        
    def __update(self):
        self.__world.Step(Constants.TIMESTEP, Constants.ITERATION_STEPS, Constants.ITERATION_POSITION)
        self.__world.ClearForces()
        
    def Rectangle(self, position, size, angle=0.0):
        bodyDef = b2BodyDef()
        bodyDef.position = (size[0] * Constants.PIXELS2METERS / 2 + position[0] * Constants.PIXELS2METERS, size[1] * Constants.PIXELS2METERS / 2 + position[1] * Constants.PIXELS2METERS)
        bodyDef.type = b2_staticBody
        shape = b2PolygonShape()
        shape.SetAsBox(size[0] * Constants.PIXELS2METERS / 2, size[1] * Constants.PIXELS2METERS / 2)
        fixture = b2FixtureDef()
        fixture.density = 1.0
        fixture.friction = 0.0
        fixture.restitution = 1.0
        fixture.shape = shape
        body = self.__world.CreateBody(bodyDef)
        body.CreateFixture(fixture)
        body.angle = angle
        return body
        
    def Circle(self, position, radius):
        bodyDef = b2BodyDef()
        bodyDef.position = (position[0] * Constants.PIXELS2METERS, position[1] * Constants.PIXELS2METERS)
        bodyDef.type = b2_staticBody
        shape = b2CircleShape()
        shape.radius = radius * Constants.PIXELS2METERS
        fixture = b2FixtureDef()
        fixture.density = 1.0
        fixture.friction = 0.0
        fixture.restitution = 1.0
        fixture.shape = shape
        body = self.__world.CreateBody(bodyDef)
        body.CreateFixture(fixture)
        return body
    
    def DestroyBody(self, body):
        self.__world.DestroyBody(body)

    def destroy(self):
        if self.__onFrameHandler:
            avg.Player.get().clearInterval(self.__onFrameHandler)
        self.__onFrameHandler = None
        self.__world = None
        self.active = False
        self.unlink(True)    
        
class CollisionListener(b2ContactListener):
    def __init__(self, **kwargs):
        b2ContactListener.__init__(self, **kwargs)
        
    def BeginContact(self, contact):
        contact.fixtureA.body.userData.onBeginContact(contact.fixtureB.body.userData)
        contact.fixtureB.body.userData.onBeginContact(contact.fixtureA.body.userData)
        
    def EndContact(self, contact):
        contact.fixtureA.body.userData.onEndContact(contact.fixtureB.body.userData)
        contact.fixtureB.body.userData.onEndContact(contact.fixtureA.body.userData)